<?php

/**
 * Jf_Controller_Router is the standard framework router.
 */
class Jf_Controller_Router {

    /**
     * Array of routes to match against
     * @var array
     */
    protected $_routes = array();

    /**
     * Currently matched route
     * @var string
     */
    protected $_current = null;

    const URI_DELIMITER = '/';

    const URI_VARIABLE = ':';

    const DEFAULT_EXT = 'php';

    const DEFAULT_ACTION = 'index';

    const DEFAULT_CONTROLLER = 'Index';

    const DEFAULT_MODULE = 'Index';

    public function __construct () {
        // $defaultRoute = Jf_G::get('default_route');
        // if ($defaultRoute != null) {
        // $this->addRoute(
        // '_default',
        // $this->_getRouteFromArray($defaultRoute)
        // );
        // } else {
        $this -> addRoute ('_default', new Jf_Controller_Route_Static ());
        // }
    }

    /**
     * Add route to the route chain
     * @param string $name Name of the route
     * @param Jf_Controller_Route_Interface $route Instance of the route
     * @return Jf_Controller_Router
     */
    public function addRoute ($name, Jf_Controller_Route_Interface $route) {
        $this -> _routes[$name] = $route;
        return $this;
    }

    /**
     * Example INI:
     * routes.archive.type = "simple"
     * routes.archive.module = Index
     * routes.archive.controller = archive
     * routes.archive.action = show
     * routes.news.type = "static"
     * routes.news.route = "news"
     * And finally after you have created a Jf_Config with above ini:
     * $router = new Jf_Controller_Router();
     * $router->addConfig($config);
     * @param array|Jf_Config $config Configuration object
     * @throws Jf_Controller_Router_Exception
     * @return Jf_Controller_Router
     */
    public function addConfig ($config) {
        if (is_array ($config)) {
            // $config = new Jf_Config_Simple($config);
        } else 
            if ($config instanceof Jf_Config_Abstract) {
                $config = $config -> toArray ();
            } else {
                throw new Jf_Exception_RouterFailed ('Expecting Array or Jf_Config_Abstract instance');
            }
        $name = key ($config);
        foreach ($config as $entry) {
            $route = $this -> _getRouteFromArray ($entry);
            if ($route != null) {
                if ($route instanceof Jf_Controller_Route_Interface) {
                    $this -> addRoute ($name, $route);
                } else {
                    $this -> addRoute ($name, $route);
                }
            } else {
                if (is_string ($name)) {
                    throw new Jf_Exception_RouterFailed ('Unable to initialize route named ' . $name);
                } else {
                    throw new Jf_Exception_RouterFailed ('Unable to initialize route at index ' . $name);
                }
            }
            $name = key ($config);
            next ($config);
        }
        /*
         * //exit(); foreach ($config as $name => $info) { $route = $this->_getRouteFromConfig($info); if ($route!=null) { if ($route instanceof Jf_Controller_Route_Interface) { $this->addRoute($name, $route); } else { $this->addRoute($name, $route); } } else { throw new Jf_Exception_RouterFailed( 'Unable to initialize route named '.$name ); } }
         */
        return $this;
    }

    /**
     * Get a route from a config instance
     * @param Jf_Config_Abstract $info
     * @return Jf_Controller_Route_Interface
     */
    protected function _getRouteFromConfig (Jf_Config_Abstract $info) {
        $useNamespace = Jf_G::iniGet ('Jf.use_namespace');
        if ($useNamespace) {
            $class = (isset ($info['type'])) ? '\\Jf\\Route\\' . ucfirst ($info['type']) : '\\Jf\\Route\\Static';
        } else {
            $class = (isset ($info['type'])) ? 'Jf_Controller_Route_' . ucfirst ($info['type']) : 'Jf_Controller_Route_Static';
        }
        try {
            $route = call_user_func (array($class, 'getInstance'), $info);
        } catch (Exception $e) {
            return null;
        }
        return $route;
    }

    /**
     * Get a route from an array
     * @param array $info
     * @return Jf_Controller_Route_Interface
     */
    protected function _getRouteFromArray (array $info) {
        $useNamespace = Jf_G::iniGet ('Jf.use_namespace');
        if ($useNamespace) {
            $class = (isset ($info['type'])) ? '\\Jf\\Route\\' . ucfirst ($info['type']) : '\\Jf\\Route\\Static';
        } else {
            $class = (isset ($info['type'])) ? 'Jf_Controller_Route_' . ucfirst ($info['type']) : 'Jf_Controller_Route_Static';
        }
        try {
            $route = call_user_func (array($class, 'getInstance'), $info);
        } catch (Exception $e) {
            return null;
        }
        return $route;
    }

    /**
     * Retrieve a currently matched route
     * @throws Jf_Exception_RouterFailed
     * @return Jf_Controller_Route_Interface Route object
     */
    public function getCurrentRoute () {
        return $this -> _current;
    }

    /**
     * Retrieve a named route
     * @param string $name Name of the route
     * @throws Jf_Exception_RouterFailed
     * @return Jf_Controller_Route_Interface Route object
     */
    public function getRoute ($name) {
        if ( ! isset ($this -> _routes[$name])) {
            return null;
            /*
             * throw new Jf_Exception_RouterFailed( "Route $name is not defined" );
             */
        }
        return $this -> _routes[$name];
    }

    /**
     * Retrieve an array of routes added to the route chain
     * @return array All of the defined routes
     */
    public function getRoutes () {
        return $this -> _routes;
    }

    /**
     * Find a matching route to the current Request and inject
     * returning values to the Request object.
     * @return bool if there is a valid route
     */
    public function route (Jf_Controller_Request_Abstract $request) {
        // Find the matching route
        $routeMatched = false;
        foreach (array_reverse ($this -> _routes, true) as $name => $route) {
            if (($ret = $route -> route ($request)) != false) {
                $this -> _current = $name;
                $routeMatched = true;
                break;
            }
        }
        if ( ! $routeMatched) {
            return false;
        } else {
            $request -> setRouted (true);
            return true;
        }
        return false;
    }

}
